package bind

import (
	"errors"
	"fmt"
	"reflect"
	"strconv"
	"strings"
	"time"
)

/*
说明：验证器是自动将map[string]interface{}的值赋对象，并对每个值的格式进行验证匹配
以保证数据输入的准确性。
使用方法：
	//创建一个结构体
	type Test struct{
		Age string `name:"年龄"` //tag用于绑定错误提示
	}
	//实现Binder接口
	func (this *Test)BindConfig()(m []*validate.V){
		m=append(m,bind.V{Attr:"Age", Check:this.checkName,Note:"输入错误"})
	}
	func (this *Test)checkName(name interface{})bool{
		return false
	}
	data:=map[string]interface{}{"Age":"28"}
	t:=new(Test)
	v:=validate.New(data)
	ok,err:=v.Give(t)
	fmt.Println(ok,err)
*/

//数据绑定
type Bind struct {
	Data map[string]string
	must map[string]bool
	omit map[string]bool
}

//必须且不能为空
func (this *Bind) Must(attr ...string) *Bind {
	for _, a := range attr {
		this.must[a] = true
	}
	return this
}

//允许为空
func (this *Bind) Omit(attr ...string) *Bind {
	for _, a := range attr {
		this.omit[a] = true
	}
	return this
}

//给o赋值并验证数据
func (this *Bind) Give(obj interface{}) (err error, attr string) {
	//验证集合
	ref := reflect.ValueOf(obj).Elem()
	return this.reflectSet(ref, ref.Type())
}

//反射赋值
func (this *Bind) reflectSet(ref reflect.Value, typ reflect.Type) (err error, attr string) {
	for i := 0; i < ref.NumField(); i++ {
		if ref.Field(i).Kind() == reflect.Struct { //如果为结构体，则循环从结构体里面去绑定参数
			return this.reflectSet(ref.Field(i), typ.Field(i).Type)
		}
		field := typ.Field(i) //
		attr = field.Name     //结构体成员属性名称
		tagName := field.Tag.Get("name")
		value := this.Data[attr]
		//不在需求表单里面的，则忽略掉
		if !this.must[attr] && !this.omit[attr] {
			continue
		}
		if this.omit[attr] && value == "" { //非必须，且为空，则忽略
			continue
		}
		if this.must[attr] && value == "" { //必须=不能为空
			return errors.New(tagName + "不能为空"), attr
		}
		valid := field.Tag.Get("valid")
		if valid == "" { //不进行验证和映射
			continue
		}
		validArray := strings.Split(valid, ",")
		//验证数据格式
		if err := this.match(this.Data[attr], validArray); !err {
			return errors.New(tagName + "提交错误"), attr
		}
		//类型断言，设置反射value
		rvalue, err := this.typeConversion(value, field.Type.Name())
		if err != nil { //如果断言转换类型出现错误
			return errors.New(tagName + "类型错误"), attr
		}
		if !ref.Field(i).CanSet() { //如果该字段不允许赋值(开发期应该注意的问题）
			e := fmt.Sprintf("%s can't set.", attr)
			panic(errors.New(e))
		}
		ref.Field(i).Set(rvalue) //给结构体赋值
		//print(matchRes, "\t", note, "\t", attr, "\t", "BindCall\n")
	}
	return nil, ""
}

//类型断言
func (this *Bind) typeConversion(value string, ntype string) (reflect.Value, error) {
	intvalue := value
	if intvalue == "" {
		intvalue = "0"
	}
	if ntype == "string" {
		return reflect.ValueOf(value), nil
	} else if ntype == "time.Time" {
		t, err := time.ParseInLocation("2006-01-02 15:04:05", value, time.Local)
		return reflect.ValueOf(t), err
	} else if ntype == "Time" {
		t, err := time.ParseInLocation("2006-01-02 15:04:05", value, time.Local)
		return reflect.ValueOf(t), err
	} else if ntype == "int" {
		i, err := strconv.ParseInt(intvalue, 10, 64)
		return reflect.ValueOf(int(i)), err
	} else if ntype == "uint" {
		i, err := strconv.ParseInt(intvalue, 10, 64)
		return reflect.ValueOf(uint(i)), err
	} else if ntype == "int8" {
		i, err := strconv.ParseInt(intvalue, 10, 64)
		return reflect.ValueOf(int8(i)), err
	} else if ntype == "int16" {
		i, err := strconv.ParseInt(intvalue, 10, 64)
		return reflect.ValueOf(int16(i)), err
	} else if ntype == "int32" {
		i, err := strconv.ParseInt(intvalue, 10, 64)
		return reflect.ValueOf(int32(i)), err
	} else if ntype == "int64" {
		i, err := strconv.ParseInt(intvalue, 10, 64)
		return reflect.ValueOf(i), err
	} else if ntype == "float32" {
		i, err := strconv.ParseFloat(intvalue, 64)
		return reflect.ValueOf(float32(i)), err
	} else if ntype == "float64" {
		i, err := strconv.ParseFloat(intvalue, 64)
		return reflect.ValueOf(i), err
	}
	//else if .......增加其他一些类型的转换
	return reflect.ValueOf(value), errors.New("未知的类型" + ntype)
}

//单项验证，如果没有需要该字段的验证，则忽略过去不映射到对应的结构体属性上去
//输入：v=值，validate=验证结构
//返回：errname=错误内容
func (this *Bind) match(v string, validate []string) bool {
	var check bool
	for _, m := range validate {
		if check = Match(v, m); !check {
			return false
		}
	}
	return true
}
